package com.bolt.support.spring.jpa.jpql;

import com.bolt.common.collection.IteratorUtil;
import com.bolt.common.ognl.OgnlCache;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.query.calculator.domain.UnpivotModel;
import com.bolt.support.query.conditions.domain.MatchType;
import com.bolt.support.query.conditions.domain.SqlClause;
import com.bolt.support.query.toolkit.JPAUtil;
import com.bolt.support.spring.jpa.entity.PersistableEntity;
import com.bolt.support.spring.jpa.specification.QueryHelp;
import com.bolt.support.sqlconfig.base.ExpressionEvaluator;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;

public abstract class BaseJPAQuery<T, Q extends JPQLQuery<T>> implements JPQLQuery<T> {

    protected final Q typedThis = (Q) this;

    protected final QueryMetadata queryMetadata;

    public BaseJPAQuery(QueryMetadata queryMetadata) {
        this.queryMetadata = queryMetadata;
    }

    @Override
    public Q from(EntityClause... sources) {
        queryMetadata.addFrom(sources);
        if (IteratorUtil.isEmpty(queryMetadata.getSelect()) || queryMetadata.isAllField()) {
            for (EntityClause tableClause : sources) {
                queryMetadata.addSelect(JPAUtil.createSelect(tableClause.getType()));
            }
        }
        return typedThis;
    }

    @Override
    public <T extends PersistableEntity> JPAQuery<T> from(Class<T> type) {
        if (Objects.isNull(queryMetadata.getReturnClass())) {
            queryMetadata.setReturnClass(type);
        }
        if (IteratorUtil.isEmpty(queryMetadata.getSelect()) || queryMetadata.isAllField()) {
            queryMetadata.addSelect(JPAUtil.createSelect(type));
        }
        from(Clauses.of(type));
        JPAQuery<T> newType = (JPAQuery<T>) this;
        return newType;
    }

    @Override
    public JPQLQuery<T> innerJoin(EntityClause target) {
        queryMetadata.addJoin(JoinType.INNERJOIN, target);
        return typedThis;
    }

    @Override
    public JPQLQuery<T> fullJoin(EntityClause target) {
        queryMetadata.addJoin(JoinType.FULLJOIN, target);
        return typedThis;
    }

    @Override
    public Q leftJoin(EntityClause target) {
        queryMetadata.addJoin(JoinType.LEFTJOIN, target);
        return typedThis;
    }

    @Override
    public Q rightJoin(EntityClause target) {
        queryMetadata.addJoin(JoinType.RIGHTJOIN, target);
        return typedThis;
    }

    @Override
    public Q on(SqlClause... columns) {
        if (columns.length % 2 != 0) {
            throw new ServiceException("Error join on expression");
        }
        for (int i = 0; i < columns.length; i = i + 2) {
            OperationClause compareClause = Clauses.addCompare(Ops.EQ, null, columns[i], columns[i + 1]);
            queryMetadata.addJoinCondition(compareClause);
        }
        return typedThis;
    }

    @Override
    public Q where(CompareClause clause) {
        queryMetadata.setWhere(clause.render(false));
        return typedThis;
    }

    @Override
    public Q where() {
        return typedThis;
    }

    @Override
    public Q where(Consumer<CompareClause> consumer) {
        CompareClause instance = new CompareClause();
        consumer.accept(instance);
        queryMetadata.setWhere(instance.render(false));
        return typedThis;
    }

    @Override
    public Q groupBy(SqlClause... target) {
        queryMetadata.addGroupBy(target);
        return typedThis;
    }

    @Override
    public Q orderBy(SqlClause... target) {
        queryMetadata.addOrderBy(target);
        return typedThis;
    }

    @Override
    public Q having(CompareClause clause) {
        queryMetadata.addHaving(clause.render(false));
        return typedThis;
    }

    @Override
    public Q limit(long limit) {
        queryMetadata.setLimit(limit);
        return typedThis;
    }

    @Override
    public Q offset(long offset) {
        queryMetadata.setOffset(offset);
        return typedThis;
    }

    @Override
    public JPQLQuery<T> and(Ops ops, ColumnClause... clauses) {
        for (ColumnClause clause : clauses) {
            queryMetadata.addDynamicConditions(new DynamicCondition(Ops.AND, ops, clause));
        }
        return typedThis;
    }


    @Override
    public JPQLQuery<T> and(String exprs, Ops ops, ColumnClause... clauses) {
        for (ColumnClause clause : clauses) {
            queryMetadata.addDynamicConditions(new DynamicCondition(Ops.AND, ops, clause, exprs));
        }
        return typedThis;
    }

    @Override
    public JPQLQuery<T> and(CompareClause... compareClauses) {
        for (CompareClause compareClause : compareClauses) {
            if (Objects.isNull(compareClause.getOpMixin())) continue;
            queryMetadata.setWhere(
                    Clauses.addCompare(Ops.AND, queryMetadata.getWhere(), compareClause.getOpMixin())
            );
        }
        return typedThis;
    }

    @Override
    public JPQLQuery<T> or(Ops ops, ColumnClause... clauses) {
        for (ColumnClause clause : clauses) {
            queryMetadata.addDynamicConditions(new DynamicCondition(Ops.OR, ops, clause));
        }
        return typedThis;
    }

    @Override
    public JPQLQuery<T> or(String exprs, Ops ops, ColumnClause... clauses) {
        for (ColumnClause clause : clauses) {
            queryMetadata.addDynamicConditions(new DynamicCondition(Ops.OR, ops, clause, exprs));
        }
        return typedThis;
    }

    @Override
    public JPQLQuery<T> or(CompareClause... compareClauses) {
        for (CompareClause compareClause : compareClauses) {
            queryMetadata.setWhere(Clauses.addCompare(Ops.OR, queryMetadata.getWhere(), compareClause.getOpMixin()));
        }

        return typedThis;
    }

    public JPQLQuery<T> buildCondition(Object parameterObject) {
        List<DynamicCondition> conditions = queryMetadata.getDynamicConditions();

        ExpressionEvaluator evaluator = new ExpressionEvaluator();
        for (DynamicCondition condition : conditions) {
            if (StrUtil.isNotBlank(condition.getTest())
                    && evaluator.evaluateBoolean(condition.getTest(), parameterObject)) {
                ColumnClause columnClause = condition.getClause();
                String paramName = columnClause.getParamName();
                try {
                    OperationClause where = queryMetadata.getWhere();
                    Object value = OgnlCache.getValue(paramName, parameterObject);
                    MatchType matchType = QueryHelp.toMatchType(condition.getOperator());
                    CompareClause compareClause = QueryHelp
                            .buildOperationClause(matchType, value, columnClause.getType(), columnClause.getProperty(), paramName);

                    OperationClause opMixin = Clauses.addCompare(condition.getConnect(), where, compareClause.render(false));
                    queryMetadata.setWhere(opMixin);
                } catch (Exception ex) {
                    throw new ServiceException(
                            "Error evaluating expression '" + paramName
                                    + "'. Cause: " + ex, ex);
                }
            }
        }
        return typedThis;
    }

    @Override
    public JPQLQuery<T> unpivot(String transformColumns, String columnsLabels) {
        UnpivotModel unpivotModel = new UnpivotModel().setTransformColumns(transformColumns).setColumnsLabels(columnsLabels);
        queryMetadata.addResultProcessor(unpivotModel);
        return typedThis;
    }

    @Override
    public JPQLQuery<T> pivot(Object parameterObject) {
        return typedThis;
    }


    protected Optional<ColumnClause> findColumnInSelect(String property) {
        ColumnClause columnClause = null;
        for (SqlClause<?> sqlClause : queryMetadata.getSelect()) {
            if (sqlClause instanceof ColumnClause) {
                columnClause = ((ColumnClause) sqlClause);
                if (columnClause.getProperty().equals(property)) ;
                break;
            }
        }
        return Optional.of(columnClause);
    }


    @Override
    public JPQLQuery<T> usedSubCount(boolean used) {
        this.queryMetadata.setSubCount(used);
        return typedThis;
    }
}

 